package geo

import "math"

/**
* 坐标转换程序
*
*  WGS84坐标系：即地球坐标系，国际上通用的坐标系。Earth

   GCJ02坐标系：即火星坐标系，WGS84坐标系经加密后的坐标系。Mars

   BD09坐标系：即百度坐标系，GCJ02坐标系经加密后的坐标系。  Bd09

   搜狗坐标系、图吧坐标系等，估计也是在GCJ02基础上加密而成的。
*
* 百度地图API        百度坐标
  腾讯搜搜地图API    火星坐标
  搜狐搜狗地图API    搜狗坐标*
  阿里云地图API       火星坐标
  图吧MapBar地图API    图吧坐标
  高德MapABC地图API    火星坐标
  灵图51ditu地图API    火星坐标
*
*/

const (
	// PI 圆周率
	PI float64 = math.Pi
	// AXIS 地球直径
	AXIS float64 = 6378245.0
	// OFFSET 偏移
	OFFSET float64 = 0.00669342162296594323 //(a^2 - b^2) / a^2
	// XPI x pi
	XPI float64 = PI * 3000.0 / 180.0
)

// GCJ2BD09 GCJ-02=>BD09 火星坐标系=>百度坐标系
func (p *Point) GCJ2BD09() *Point {
	/**
	  	public static double[] gcj2BD09(double glat, double glon){
	          double x = glon;
	          double y = glat;
	          double[] latlon = new double[2];
	          double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI);
	          double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI);
	          latlon[0] = z * Math.sin(theta) + 0.006;
	          latlon[1] = z * Math.cos(theta) + 0.0065;
	          return latlon;
	      }
	*/
	x := p.Lng
	y := p.Lat
	z := math.Sqrt(x*x+y*y) + 0.00002*math.Sin(y*XPI)
	theta := math.Atan2(y, x) + 0.000003*math.Cos(x*XPI)
	return &Point{
		Lat: z*math.Sin(theta) + 0.006,
		Lng: z*math.Cos(theta) + 0.0065,
	}
}

// BD092GCJ 百度坐标系=>火星坐标系
func (p *Point) BD092GCJ() *Point {
	/**
	public static double[] bd092GCJ(double glat, double glon){
		double x = glon - 0.0065;
		double y = glat - 0.006;
		double[] latlon = new double[2];
		double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
		double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
		latlon[0] = z * Math.sin(theta);
		latlon[1] = z * Math.cos(theta);
		return latlon;
	}*/
	x := p.Lng - 0.0065
	y := p.Lat - 0.006
	z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*XPI)
	theta := math.Atan2(y, x) - 0.000003*math.Cos(x*XPI)
	return &Point{
		Lat: z * math.Sin(theta),
		Lng: z * math.Cos(theta),
	}
}

//BD092WGS BD09=>WGS84 百度坐标系=>地球坐标系
func (p *Point) BD092WGS() *Point {
	// public static double[] bd092WGS(double glat, double glon){
	// 	double[] latlon = bd092GCJ(glat,glon);
	// 	return gcj2WGS(latlon[0],latlon[1]);
	// }
	tmp := p.BD092GCJ()
	return tmp.GCJ2WGS()
}

// WGS2BD09 WGS84=》BD09   地球坐标系=>百度坐标系
func (p *Point) WGS2BD09() *Point {
	// public static double[] wgs2BD09(double wgLat, double wgLon) {
	// 	double[] latlon = wgs2GCJ(wgLat,wgLon);
	// 	return gcj2BD09(latlon[0],latlon[1]);
	// }
	tmp := p.WGS2GCJ()
	return tmp.GCJ2BD09()
}

// WGS2GCJ WGS84=》GCJ02   地球坐标系=>火星坐标系
func (p *Point) WGS2GCJ() *Point {
	// public static double[] wgs2GCJ(double wgLat, double wgLon) {
	// 	double[] latlon  = new double[2];
	// 	if (outOfChina(wgLat, wgLon)){
	// 		latlon[0] = wgLat;
	// 		latlon[1] = wgLon;
	// 	   return latlon;
	// 	 }
	// 	double[] deltaD =  delta(wgLat,wgLon);
	// 	latlon[0] = wgLat + deltaD[0];
	// 	latlon[1] = wgLon + deltaD[1];
	// 	return latlon;
	// }
	// double[] latlon  = new double[2];
	if p.outOfChina() {
		return p
	}
	deltaD := p.delta()
	return &Point{
		Lat: p.Lat + deltaD.Lat,
		Lng: p.Lng + deltaD.Lng,
	}
}

// GCJ2WGS  火星坐标系=>地球坐标系(粗略)
func (p *Point) GCJ2WGS() *Point {
	// public static double[] gcj2WGS(double glat,double glon){
	// 	double[] latlon  = new double[2];
	// 	if (outOfChina(glat, glon)){
	// 		latlon[0] = glat;
	// 		latlon[1] = glon;
	// 	   return latlon;
	// 	 }
	// 	double[] deltaD =  delta(glat,glon);
	// 	latlon[0] = glat - deltaD[0];
	// 	latlon[1] = glon - deltaD[1];
	// 	return latlon;
	// }
	if p.outOfChina() {
		return p
	}
	deltaD := p.delta()
	return &Point{
		Lat: p.Lat - deltaD.Lat,
		Lng: p.Lng - deltaD.Lng,
	}
}

//GCJ2WGSExactly GCJ02=>WGS84   火星坐标系=>地球坐标系（精确）
func (p *Point) GCJ2WGSExactly() *Point {
	// public static double[] gcj2WGSExactly(double gcjLat,double gcjLon){
	// 	double initDelta = 0.01;
	// 	double threshold = 0.000000001;
	// 	double dLat = initDelta, dLon = initDelta;
	// 	double mLat = gcjLat - dLat, mLon = gcjLon - dLon;
	// 	double pLat = gcjLat + dLat, pLon = gcjLon + dLon;
	// 	double wgsLat, wgsLon, i = 0;
	// 	while (true) {
	// 		wgsLat = (mLat + pLat) / 2;
	// 		wgsLon = (mLon + pLon) / 2;
	// 		double[] tmp = wgs2GCJ(wgsLat, wgsLon);
	// 		dLat = tmp[0] - gcjLat;
	// 		dLon = tmp[1] - gcjLon;
	// 		if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold))
	// 			break;

	// 		if (dLat > 0) pLat = wgsLat; else mLat = wgsLat;
	// 		if (dLon > 0) pLon = wgsLon; else mLon = wgsLon;

	// 		if (++i > 10000) break;
	// 	}
	// 	double[] latlon = new double[2];
	// 	latlon[0] = wgsLat;
	// 	latlon[1] = wgsLon;
	// 	return latlon;
	// }
	const initDelta float64 = 0.01
	const threshold float64 = 0.000000001
	dLat, dLon := initDelta, initDelta
	mLat := p.Lat - dLat
	mLon := p.Lng - dLon
	pLat := p.Lat + dLat
	pLon := p.Lng + dLon
	var wgsLat, wgsLon float64
	for n := 0; n <= 10000; n++ {
		wgsLat = (mLat + pLat) / 2
		wgsLon = (mLon + pLon) / 2
		wgs := &Point{Lat: wgsLat, Lng: wgsLon}
		tmp := wgs.WGS2GCJ()
		dLat = tmp.Lat - p.Lat
		dLon = tmp.Lng - p.Lng
		if (math.Abs(dLat) < threshold) && (math.Abs(dLon) < threshold) {
			break
		}

		if dLat > 0 {
			pLat = wgsLat
		} else {
			mLat = wgsLat
		}
		if dLon > 0 {
			pLon = wgsLon
		} else {
			mLon = wgsLon
		}
	}
	return &Point{
		Lat: wgsLat,
		Lng: wgsLon,
	}
}

// Distance 两点距离
func (p *Point) Distance(o *Point) float64 {
	//两点距离
	// public static double distance(double latA, double logA, double latB,double  logB){
	// 	int earthR = 6371000;
	// 	double x = Math.cos(latA*Math.PI/180) * Math.cos(latB*Math.PI/180) * Math.cos((logA-logB)*Math.PI/180);
	// 	double y = Math.sin(latA*Math.PI/180) * Math.sin(latB*Math.PI/180);
	// 	double s = x + y;
	// 	if (s > 1)
	// 		s = 1;
	// 	if (s < -1)
	// 		s = -1;
	// 	double alpha = Math.acos(s);
	// 	double distance = alpha * earthR;
	// 	return distance;
	// }
	const earthR = 6371000
	x := math.Cos(p.Lat*PI/180) * math.Cos(o.Lat*PI/180) * math.Cos((p.Lng-o.Lng)*PI/180)
	y := math.Sin(p.Lat*PI/180) * math.Sin(o.Lat*PI/180)
	s := x + y
	if s > 1 {
		s = 1
	}
	if s < -1 {
		s = -1
	}
	alpha := math.Acos(s)
	distance := alpha * earthR
	return distance
}

func (p *Point) delta() *Point {
	/**
		 public static double[] delta(double wgLat, double wgLon){
			double[] latlng  = new double[2];
			double dLat = transformLat(wgLon - 105.0, wgLat - 35.0);
			double dLon = transformLon(wgLon - 105.0, wgLat - 35.0);
			double radLat = wgLat / 180.0 * PI;
			double magic = Math.sin(radLat);
			magic = 1 - OFFSET * magic * magic;
			double sqrtMagic = Math.sqrt(magic);
			dLat = (dLat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtMagic) * PI);
			dLon = (dLon * 180.0) / (AXIS / sqrtMagic * Math.cos(radLat) * PI);
			latlng[0] =dLat;
			latlng[1] =dLon;
			return latlng;
	}
	*/
	// double[] latlng  = new double[2];
	dLat := p.transformLat(p.Lng-105.0, p.Lat-35.0)
	dLon := p.transformLon(p.Lng-105.0, p.Lat-35.0)
	radLat := p.Lat / 180.0 * PI
	magic := math.Sin(radLat)
	magic = 1 - OFFSET*magic*magic
	sqrtMagic := math.Sqrt(magic)
	dLat = (dLat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtMagic) * PI)
	dLon = (dLon * 180.0) / (AXIS / sqrtMagic * math.Cos(radLat) * PI)
	return &Point{
		Lat: dLat,
		Lng: dLon,
	}
}

func (p *Point) outOfChina() bool {
	// public static boolean outOfChina(double lat, double lon){
	//    if (lon < 72.004 || lon > 137.8347)
	// 	   return true;
	//    if (lat < 0.8293 || lat > 55.8271)
	// 	   return true;
	//    return false;
	// }
	if p.Lng < 72.004 || p.Lng > 137.8347 {
		return true
	}
	if p.Lat < 0.8293 || p.Lat > 55.8271 {
		return true
	}
	return false
}
func (p *Point) transformLat(x, y float64) float64 {
	// public static double transformLat(double x, double y){
	//    double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
	//    ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
	//    ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
	//    ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
	//    return ret;
	// }
	ret := -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*math.Sqrt(math.Abs(x))
	ret += (20.0*math.Sin(6.0*x*PI) + 20.0*math.Sin(2.0*x*PI)) * 2.0 / 3.0
	ret += (20.0*math.Sin(y*PI) + 40.0*math.Sin(y/3.0*PI)) * 2.0 / 3.0
	ret += (160.0*math.Sin(y/12.0*PI) + 320*math.Sin(y*PI/30.0)) * 2.0 / 3.0
	return ret
}

func (p *Point) transformLon(x, y float64) float64 {
	// public static double transformLon(double x, double y){
	//    double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
	//    ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
	//    ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
	//    ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
	//    return ret;
	// }
	ret := 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*math.Sqrt(math.Abs(x))
	ret += (20.0*math.Sin(6.0*x*PI) + 20.0*math.Sin(2.0*x*PI)) * 2.0 / 3.0
	ret += (20.0*math.Sin(x*PI) + 40.0*math.Sin(x/3.0*PI)) * 2.0 / 3.0
	ret += (150.0*math.Sin(x/12.0*PI) + 300.0*math.Sin(x/30.0*PI)) * 2.0 / 3.0
	return ret
}
